package com.mfz.common.config;

import com.alibaba.fastjson.JSONObject;
import com.mfz.common.StringUtil;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
import org.apache.logging.log4j.core.util.Loader;

import java.util.HashMap;
import java.util.Map;

/**
 * 读取配置文件的公用方法.
 * <p>
 * 目前配置文件支持json格式的文件。
 * 查找文件的方式:
 * 应用程序资源目录->引用包内资源目录(多级)->本包内资源目录
 *
 * @author yimin
 */

public class Config {
	private final static Logger logger = LogManager.getLogger(Config.class.getName());
	private final static Map<String, Config> configs = new HashMap<>();

	private static Config defaultConfig;
	private final String separator = ".";

	private JSONObject content;
	private String configName = "";
	private final String strings;

	private Config(String fileName) {
		strings = StringUtil.getProperties(Loader.getResource(fileName, Config.class.getClassLoader()), "UTF-8");

		content = JSONObject.parseObject(strings);
	}

	@Override
	public String toString() {
		return strings;
	}

	/**
	 * 得到此配置的所有内容
	 */
	public JSONObject getContent() {
		return content;
	}

	/**
	 * 从指定的文件名读一个配置文件，并根据指定名称存放.
	 * <p>
	 * 如多次调用此方法，会重新读指定文件名内容。
	 * <p>
	 * 如每次指定name 不同，将会多次存放文件的多个副本.
	 *
	 * @param name     在此配置文件内的名称
	 * @param fileName 需要读取的文件名
	 * @return 新创建的配置文件
	 */
	public static Config createConfig(String name, String fileName) {
		if (configs.containsKey(name)) {
			logger.warn("已经存在同名对象.直接返回原有对象");
			return configs.get(name);
		}

		logger.trace("新增加一个配置文件，名称:{}，读取文件名:{}", name, fileName);
		Config config = new Config(fileName);

		synchronized (configs) {
			configs.put(name, config);
			config.configName = name;

			if (configs.size() == 1) {
				defaultConfig = config;
			}
		}

		return config;
	}

	/**
	 * 找到指定名称的配置对角
	 *
	 * @param name 指定的名称，此名称为创建对象时指定的名称
	 * @return Confing对象。如果未找到返回null
	 */
	public static Config getIns(String name) {
		return configs.get(name);
	}


	/**
	 * 返回指定名称的配置项.
	 *
	 * @param name         指定的配置名称.
	 * @param defaultValue 当未定义值时使用的默认值
	 * @return 配置项的值
	 */
	public <T> T get(String name, T defaultValue) {
		T value = get(name);

		return value != null ? value : defaultValue;
	}


	/**
	 * 返回指定名称的配置项.
	 *
	 * @param name     指定的配置名称.
	 * @param notFound 当名称在配置文件中未定义时，回调。
	 * @return 配置项的值
	 */
	public <T> T get(String name, NotParaCallback notFound) {
		T value = get(name);

		if (value == null) {
			logger.error("未找到配置的名称项，配置名:{}，项目名:{}", configName, name);
			notFound.callBack(configName + "->" + name + "\n");
		}

		return value;
	}

	/**
	 * 返回指定名称的配置项.
	 *
	 * @param name 指定的配置名称.
	 * @return 配置项的值
	 */
	public <T> T get(String name) {
		//noinspection ConstantConditions
		Object value = locate(name).get(getLastName(name));
		if (value == null) {
			logger.warn("未找到指定名称:{}的配置项。", name);
			return null;
		}

		try {
			//noinspection unchecked
			return (T) value;
		} catch (Exception e) {
			logger.error("配置项值与请求配置值不相符。名称:{}.返回空", name);
			return null;
		}
	}

	/**
	 * 校验值是否存在.
	 *
	 * @param names 需要校验的名称
	 * @return 当找到第一个未存在的名称时返回。
	 */
	public String checkContent(String... names) {
		for (String name : names) {
			//noinspection ConstantConditions
			if (!locate(name).containsKey(getLastName(name))) {
				return name;
			}
		}

		return null;
	}


	private JSONObject locate(String key) {
		if (!key.contains(separator)) {
			//如果未有"."分隔符
			return content;
		}

		JSONObject temp = content;
		int index = 0, endIndex;

		while ((endIndex = key.indexOf(separator, index)) > 0) {
			if ((temp = getMap(key.substring(index, endIndex), temp)) == null) {
				return null;
			}
			index = endIndex + 1;
		}

		return temp;
	}

	private JSONObject getMap(String key, JSONObject content) {
		Object object = content.get(key);
		if (object instanceof JSONObject) {
			return (JSONObject) object;
		}

		return null;
	}

	private String getLastName(String key) {
		int index;
		if ((index = key.lastIndexOf(separator)) == -1) {
			return key;
		}
		return key.substring(index + 1, key.length());
	}

	public static void setDefaultConfig(Config defaultConfig) {
		Config.defaultConfig = defaultConfig;
	}

	public static Config getDefaultConfig() {
		return defaultConfig;
	}

}
